/****************************** Module Header ******************************\
* Module Name:  SampleService.cpp
* Project:      CppInteractiveWindowsService
* Copyright (c) Microsoft Corporation.
* 
* Provides a sample service class that derives from the service base class - 
* CServiceBase. The sample service logs the service start and stop 
* information to the Application event log, and shows how to run the main 
* function of the service in a thread pool worker thread.
* 
* This source is subject to the Microsoft Public License.
* See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
* All other rights reserved.
* 
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
* EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
* WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
\***************************************************************************/

#include "stdafx.h"

#include "Common.h"

#include <WinSock2.h>
#include <ws2tcpip.h>

#pragma comment (lib, "Ws2_32.lib")

#pragma region Includes
#include "SampleService.h"
#include "ThreadPool.h"
#include <wtsapi32.h>
#pragma comment(lib, "wtsapi32.lib")
#include <userenv.h>
#pragma comment(lib, "userenv.lib")
#pragma endregion

#include "..\Engine\Engine.h"
#include "..\EndGameDB\EndGameDb.h"

#include "DBServiceStackWalker.h"

using namespace EndGameDB;

 // info parameters
/////////////////////////////////////////////////////////////////////////////////////////

HANDLE hMutex;

int maxSubDb;
int dbIndex;
wchar_t sDbName[1000];
wchar_t sProcessName[1000];
int iPercent;



 // Progress receiving functions
////////////////////////////////////////////////////////////////////////////////////////

	void ProgressDb(int wm, int wk, int bm, int bk, int wmrank, int bmrank)
	{
		::WaitForSingleObject(hMutex, INFINITE);
		dbIndex++;
		swprintf_s(sDbName, 1000, L"Building [%d,%d,%d,%d](%d,%d)", wm, wk, bm, bk, wmrank, bmrank);
		::ReleaseMutex(hMutex);
	}


	void ProgressPercentProc(int percent)
	{
		::WaitForSingleObject(hMutex, INFINITE);
		iPercent = percent;
		::ReleaseMutex(hMutex);
	}

	void ProcessNameStarted(wchar_t *procname)
	{
		::WaitForSingleObject(hMutex, INFINITE);
		wcscpy_s(sProcessName, 1000, procname);
		::ReleaseMutex(hMutex);
	}



 // The Computing Service
////////////////////////////////////////////////////////////////////////////////////////


CSampleService::CSampleService(PWSTR pszServiceName, 
                               BOOL fCanStop, 
                               BOOL fCanShutdown, 
                               BOOL fCanPauseContinue)
: CServiceBase(pszServiceName, fCanStop, fCanShutdown, fCanPauseContinue)
{
    // Create a manual-reset event that is not signaled at first. It is 
    // signaled when the main function of the service is finished.
    m_hFinishedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (m_hFinishedEvent == NULL)
    {
        throw GetLastError();
    }

    m_hFinishedEvent2 = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (m_hFinishedEvent == NULL)
    {
        throw GetLastError();
    }

	hMutex = ::CreateMutex(NULL, FALSE,	SYNCH_MUTEX_NAME);
	if(hMutex == INVALID_HANDLE_VALUE)
	{
		throw GetLastError();
	}
}


CSampleService::~CSampleService(void)
{
    if (m_hFinishedEvent)
    {
        CloseHandle(m_hFinishedEvent);
        m_hFinishedEvent = NULL;
    }
    if (m_hFinishedEvent2)
    {
        CloseHandle(m_hFinishedEvent);
        m_hFinishedEvent = NULL;
    }

	CloseHandle(hMutex);
}


//
//   FUNCTION: CSampleService::OnStart(DWORD, LPWSTR *)
//
//   PURPOSE: The function is executed when a Start command is sent to the 
//   service by the SCM or when the operating system starts (for a service 
//   that starts automatically). It specifies actions to take when the 
//   service starts. In this code sample, OnStart logs a service-start 
//   message to the Application log, and queues the main service function for 
//   execution in a thread pool worker thread.
//
//   PARAMETERS:
//   * dwArgc   - number of command line arguments
//   * lpszArgv - array of command line arguments
//
//   NOTE: A service application is designed to be long running. Therefore, 
//   it usually polls or monitors something in the system. The monitoring is 
//   set up in the OnStart method. However, OnStart does not actually do the 
//   monitoring. The OnStart method must return to the operating system after 
//   the service's operation has begun. It must not loop forever or block. To 
//   set up a simple monitoring mechanism, one general solution is to create 
//   a timer in OnStart. The timer would then raise events in your code 
//   periodically, at which time your service could do its monitoring. The 
//   other solution is to spawn a new thread to perform the main service 
//   functions, which is demonstrated in this code sample.
//
void CSampleService::OnStart(DWORD dwArgc, LPWSTR *lpszArgv)
{
    // Log a service start message to the Application log.
    WriteEventLogEntry(L"DBComputingService in OnStart", 
        EVENTLOG_INFORMATION_TYPE);

    // Queue the main service function for execution in a worker thread.
	CThreadPool::QueueUserWorkItem(&CSampleService::ServiceWorkerThread, this);
	CThreadPool::QueueUserWorkItem(&CSampleService::SocketThread, this);
}




//
//   FUNCTION: CSampleService::ServiceWorkerThread(void)
//
//   PURPOSE: The method performs the main function of the service. It runs 
//   on a thread pool worker thread.
//


//
//   FUNCTION: CSampleService::OnStop(void)
//
//   PURPOSE: The function is executed when a Stop command is sent to the 
//   service by SCM. It specifies actions to take when a service stops 
//   running. In this code sample, OnStop logs a service-stop message to the 
//   Application log, and waits for the finish of the main service function.
//
//   COMMENTS:
//   Be sure to periodically call ReportServiceStatus() with 
//   SERVICE_STOP_PENDING if the procedure is going to take long time. 
//
void CSampleService::OnStop()
{
    // Log a service stop message to the Application log.
    WriteEventLogEntry(L"DBComputingService in OnStop", 
        EVENTLOG_INFORMATION_TYPE);
	
	// signal the thread to end
	SetTerminationFlag();
	SendTermination();

    // Wait for the finish of the main service function (ServiceWorkerThread).
    if (WaitForSingleObject(m_hFinishedEvent, INFINITE) != WAIT_OBJECT_0)
    {
        throw GetLastError();
    }
    // Wait for the finish of the main service function (SocketThread).
    if (WaitForSingleObject(m_hFinishedEvent2, INFINITE) != WAIT_OBJECT_0)
    {
        throw GetLastError();
    }

}


void CSampleService::OnPause()
{
	::SuspendThread(m_hWorkerThread);

	__super::OnPause();
}

void CSampleService::OnContinue()
{
	::ResumeThread(m_hWorkerThread);

	__super::OnContinue();
}


void CSampleService::ServiceWorkerThread()
{
	m_hWorkerThread = ::GetCurrentThread();

	//::SetPriorityClass(::GetCurrentProcess(), HIGH_PRIORITY_CLASS);
	::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
	//::SetProcessAffinityMask(::GetCurrentProcess(), 2);
	//::SetThreadAffinityMask(::GetCurrentThread(), 2);

	start:;
	__try
	{
		// configuration
		int maxpiece = 6;
		int maxthreads = 6;

		const int maxRank = 7;
		const int bitsPerRow = 4;
		const int maxField = 31;

		// clear termination
		dbIndex = 0;
		maxSubDb = GetSubDbCount<bitsPerRow, maxRank, maxField>(maxpiece, NULL, NULL);
		ClearTerminationFlag();

		// set pointers
		OnBuildSubDbStartedPtr = ProgressDb;
		PercentProgress = ProgressPercentProc;
		ProcessStartedPtr = ProcessNameStarted;
	
		// set raw DB directory
		SetDatabaseRawDirectory(L"c:\\RawCzech");

		// do initialization
		InitEngine();
		InitEndGameDb<4, 31>();

		// set memory limit
		SetDbRawMemoryLimit(1*1024);

		// set number of computing threads
		omp_set_num_threads(maxthreads);

		// do building
		BuildEndgameDatabase<BitBoard, maxField, bitsPerRow, maxRank>(maxpiece, eCheckersType::CzechCheckers);

		UnloadAll();

		// LogTimerOutput("c:\\timer2.log");
	}
	__except(ExpFilter(GetExceptionInformation(), GetExceptionCode()))
	{
		UnloadAll();
		goto start;
	}

    // Signal the finished event.
    SetEvent(m_hFinishedEvent);
}

void CSampleService::SocketThread(void)
{
	// start sockets
	WSADATA wsaData;
	int iResult = ::WSAStartup(MAKEWORD(2,2), &wsaData);
	if(iResult != 0)
	{
		WriteErrorLogEntry(L"WSAStartup failed.");
		goto Exit;
	}

	// create socket
	struct addrinfo *result = NULL;
    struct addrinfo hints;

	ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

	iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
	if ( iResult != 0 ) {
		WriteErrorLogEntry(L"getaddrinfo failed.");
		goto Exit;
	}

	SOCKET ListenSocket = INVALID_SOCKET;
	ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        freeaddrinfo(result);
		WriteErrorLogEntry(L"socket() failed.");
		goto Exit;
    }

	// Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
		WriteErrorLogEntry(L"bind() failed.");
        freeaddrinfo(result);
        closesocket(ListenSocket);
		goto Exit;
    }

    freeaddrinfo(result);

Listen:;
	iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
		WriteErrorLogEntry(L"listen() failed.");
        closesocket(ListenSocket);
		goto Exit;
    }

	SOCKET ClientSocket = INVALID_SOCKET;

	// Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
		WriteErrorLogEntry(L"accept() failed.");
        closesocket(ListenSocket);
        goto Exit;
    }

	// receive data
	char recvbuf[1];
	iResult = recv(ClientSocket, recvbuf, 1, 0);


	if(recvbuf[0] == 'E')
	{
		closesocket(ClientSocket);
		closesocket(ListenSocket);
		goto Exit;
	}

	if(recvbuf[0] != 'D')
	{
		closesocket(ClientSocket);
		goto Listen;
	}

	::WaitForSingleObject(hMutex, INFINITE);
	StatusAnswer status;
	status.dbIndex = dbIndex;
	status.iPercent = iPercent;
	status.maxSubDb = maxSubDb;
	wcscpy_s((wchar_t*)&status.sDbName, 1000, (wchar_t*)sDbName);
	wcscpy_s((wchar_t*)&status.sProcessName, 1000, (wchar_t*)sProcessName);
	::ReleaseMutex(hMutex);

	send( ClientSocket, (const char*)&status, sizeof(status), 0 );
	closesocket(ClientSocket);

	goto Listen;


Exit:
    WSACleanup();

	// Signal the finished event.
    SetEvent(m_hFinishedEvent2);
}


void CSampleService::SendTermination()
{
	// create socket
	struct addrinfo *result = NULL;
    struct addrinfo hints;

	ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

	int iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result);
	if ( iResult != 0 ) {
		WriteErrorLogEntry(L"getaddrinfo failed.");
		goto Exit;
	}

	// create sockect
	SOCKET ConnectSocket = INVALID_SOCKET;
	ConnectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ConnectSocket == INVALID_SOCKET) {
		WriteErrorLogEntry(L"socket() failed.");
		goto Exit;
    }

	// Connect to server.
    iResult = connect( ConnectSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
		WriteErrorLogEntry(L"connect() failed.");
		closesocket(ConnectSocket);
		goto Exit;
    }

	// send termination message
	char msg[1];
	msg[0] = 'E';
	send(ConnectSocket, msg, 1, 0);

	closesocket(ConnectSocket);

	Exit:;
}





